import {Detail} from "../classes/MyUI.js";
import {generateElement, getAbsoluteElementPosition} from "../functions/common.js";

const detail = new Detail(
    "多级反馈调度",
    [
        "By Louis，2021-01-11",
        "进程调度的算法有很多，因为多级反馈调度算法是 Linux 操作系统采用的算法，这里进行演示！",
        "这里默认采用三级队列，每个队列都采用 FCFS 原则生成，并采用时间片轮转调度算法"
    ],
    {
        margin: "30px 40px"
    }
);
document.getElementById("detail").appendChild(detail.getElement());

// get the showing_area on the page.
let showArea = document.getElementById("showing_area");

// all task
let allTask = [[], [], []];

// detail config
const config = {
    timeSlice: 20, // time the system run
    queueSlice: [200, 500, 1000],
    taskWidth: 100, // the width of the div on behalf of the task
    taskHeight: 100, // the width of the div on behalf of the task
    beginX: 80,
    beginY: [100, 250, 400]
};

const transPosition = (x, y) => {
    return {
        x: config.beginX + (x) * (config.taskWidth + 40),
        y: config.beginY[y]
    }
};

// define a task
class Task {
    constructor(beginTime, lastTime, taskName) {
        // The time when the task begins.
        // How long will the task last.
        this.beginTime = beginTime;
        this.lastTime = lastTime;
        this.endTime = null; // the time when the task end
        this.remainTime = lastTime; // the time remain
        this.runTime = 0; // the running time in this turn
        this.taskName = taskName; // the name of this task
        this.elem = generateElement({
            type: "div",
            width: String(config.taskWidth) + "px",
            height: String(config.taskHeight) + "px",
            border: "1px dotted #eeeeee",
            position: "absolute",
            backgroundColor: "#5FB878",
            padding: "5px 5px",
            top: "-" + String(config.taskHeight + 30) + "px",
            left: "-" + String(config.taskWidth + 30) + "px",
        });
        this.programNameP = generateElement({type: "p", innerText: taskName });
        this.beginTimeP = generateElement({type: "p", innerText: "begin:" + String(beginTime) });
        this.lastTimeP = generateElement({type: "p", innerText: "last:" + String(lastTime) });
        this.remainTimeP = generateElement({type: "p", innerText: "remain:" + String(lastTime.toFixed(2)) });
        this.elem.appendChild(this.programNameP);
        this.elem.appendChild(this.beginTimeP);
        this.elem.appendChild(this.lastTimeP);
        this.elem.appendChild(this.remainTimeP);
        this.isRunning = false;
    }

    runFor(timeSlice) {
        this.remainTime -= timeSlice;
        this.runTime += timeSlice;
        this.setBgColor("#FF5722");
        this.remainTimeP.innerText = "remain:" + String(Math.abs(this.remainTime.toFixed(2)));
    }

    isOver() {
        return this.remainTime <= 0;
    }

    pause() {
        this.runTime = 0;
        this.isRunning = false;
        this.setBgColor("#5FB878");
    }

    end(time) {
        this.setBgColor("#5FB878");
        this.endTime = time;
        this.isRunning = false;
    }

    fullTime(time) {
        return this.runTime >= time;
    }

    begin(time) {
        this.isRunning = true;
    }

    setBgColor(color) {
        this.elem.style.backgroundColor = color;
    }
}

// the input seq.
// the seq should be sorted by begin time.
let input = [
    new Task(0, 1, "P1"),
    new Task(1, 2, "P2"),
    new Task(1, 3, "P3"),
    new Task(4, 1, "P4"),
    new Task(7, 2, "P5"),
];
let end_tasks = [];
// the system time
let systemTime = 0;

// a div showing the current time
let timeShowingDiv = generateElement({
    type: "div",
    innerText: "Time:" + String((0).toFixed(2)),
});
showArea.appendChild(timeShowingDiv);
const changeTimeShowing = () => {
    timeShowingDiv.innerText = "Time:" + String(systemTime.toFixed(2));
};

const init = () => {
    input.map((task) => {
        showArea.appendChild(task.elem);
        return null;
    });
    systemTime = 0;
};

// init
init();

const moveOnComplete = (anim) => {
    if (timer === null) {
        beginInterval();
    }
};

const newTaskGetInFirst = (task, callBack) => {
    stopInterval();
    if (task instanceof Task) {
        let {x, y} = transPosition(0, allTask[0].length - 1);
        let {x: xx, y: yy} = getAbsoluteElementPosition(task.elem);
        anime({
            targets: task.elem,
            translateX: x - xx,
            translateY: y - yy,
            complete: (anim) => {
                moveOnComplete(anim);
                if (callBack) callBack();
            },
            easing: 'easeInOutQuad',
        })
    }
};

const taskMoveFromOldToNew = (task, oldQueueId, newQueueId, callBack) => {
    stopInterval();
    if (task instanceof Task) {
        for (let i = 0; i < allTask[oldQueueId].length; i++) {
            let elem = allTask[oldQueueId][i].elem;
            let {x, y} = transPosition(oldQueueId, i);
            let {x: xx, y: yy} = getAbsoluteElementPosition(elem);
            anime({
                targets: elem,
                translateX: x - xx,
                translateY: y - yy,
                easing: 'easeInOutQuad',
            })
        }
        for (let i = 0; i < allTask[newQueueId].length; i++) {
            let elem = allTask[newQueueId][i].elem;
            let {x, y} = transPosition(newQueueId, i);
            let {x: xx, y: yy} = getAbsoluteElementPosition(elem);
            anime({
                targets: elem,
                translateX: x - xx,
                translateY: y - yy,
                complete: (value) => {
                    if (elem === task.elem) {
                        moveOnComplete(value);
                    }
                    if (callBack)callBack();
                },
                easing: 'easeInOutQuad',
            })
        }
    }
};

const taskEnd = (task, queueId, callBack) => {
    if (task instanceof Task) {
        stopInterval();
        anime({
            targets: task.elem,
            translateX: 0,
            translateY: 0,
            complete: (anim)=>{
                moveOnComplete(anim);
                if (callBack)callBack();
            },
            easing: 'easeInOutQuad',
        })
    }
    for (let i = 0; i < allTask[queueId].length; i++) {
        let elem = allTask[queueId][i].elem;
        let {x, y} = transPosition(queueId, i);
        let {x: xx, y: yy} = getAbsoluteElementPosition(elem);
        anime({
            targets: elem,
            translateX: x - xx,
            translateY: y - yy,
            complete: (anim)=>{
                moveOnComplete(anim);
                if (callBack) callBack();
            },
            easing: 'easeInOutQuad',
        })
    }
};

const taskOnThirdQueuePause = (callBack) => {
    stopInterval();
    for (let i = 0; i < allTask[2].length; i++) {
        let elem = allTask[2][i].elem;
        let {x, y} = transPosition(2, i);
        let {x: xx, y: yy} = getAbsoluteElementPosition(elem);
        anime({
            targets: elem,
            translateX: x - xx,
            translateY: y - yy,
            complete: (value) => {
                if (i === allTask[2].length - 1) {
                    moveOnComplete(value);
                }
                if (callBack)callBack();
            },
            easing: 'easeInOutQuad',
        })
    }
};

// timer
let timer = null;
// the running period.
let period = 0;
// the task running
let runningTask = null;
let runningQueueId = -1;
// the function every time slice runs.
const intervalFunction = () => {
    if (period === 0) {
        systemTime += config.timeSlice / 1000;
        changeTimeShowing();
        if (runningTask) {
            runningTask.runFor(config.timeSlice / 1000);
        }
        if (runningTask && runningTask.isOver()) {
            runningTask.end(systemTime);
            end_tasks.push(runningTask);
            allTask[runningQueueId].shift();
            period = 1;
            taskEnd(runningTask, runningQueueId);
            runningTask = null;
        }
        period = 1;
    }
    if (period === 1) {
        if (runningTask && runningTask.fullTime(config.queueSlice[runningQueueId] / 1000)) {
            runningTask.pause();
            runningTask = null;
            if (runningQueueId < 2) {
                let task = allTask[runningQueueId].shift();
                allTask[runningQueueId + 1].push(task);
                period = 2;
                taskMoveFromOldToNew(task, runningQueueId, runningQueueId + 1)
            } else {
                let task = allTask[2].shift();
                allTask[2].push(task);
                period = 2;
                taskOnThirdQueuePause();
            }
        }
        period = 2;
    }
    if (period === 2) {
        if (input.length > 0 && systemTime >= input[0].beginTime) {
            allTask[0].push(input[0]);
            period = 3;
            newTaskGetInFirst(input[0]);
            input.shift();
        }
        period = 3;
    }
    let nextRunningTask = null;
    for (let i = 0; i < 3; i++) {
        if (allTask[i].length > 0) {
            nextRunningTask = allTask[i][0];
            runningQueueId = i;
            break;
        }
    }
    if (period === 3) {
        if (nextRunningTask) {
            if (runningTask && runningTask !== nextRunningTask) {
                runningTask.pause();
                runningTask = nextRunningTask;
            }
            else if (!runningTask) {
                runningTask = nextRunningTask;
            }
            nextRunningTask.begin();
        }
        else {
            if (input.length === 0) {
                stopInterval();
                console.log("There is no more tasks to be run.");
                console.log(end_tasks);
            }
        }
        period = 0;
    }
};

const beginInterval = () => {
    if (timer) {
        stopInterval();
    } else {
        timer = setInterval(() => {
            try {
                intervalFunction();
            } catch (e) {
                stopInterval();
                console.error("Error occurred:", e);
            }
        }, config.timeSlice);
    }
};

const stopInterval = () => {
    window.clearInterval(timer);
    timer = null;
};

// Layui config.
layui.use(["jquery", "form", "layer"], () => {
    let $ = layui.jquery;
    let form = layui.form;
    form.on('submit(formDemo1)', function (data) {
        try {
            if (data && data.field) {
                let {seq} = data.field;
            }
        } catch (e) {
            console.log(e);
        }
        return false;
    });
    $("#begin_stop_canvas_btn").on("click", (e) => {
        console.log(e);
        if (timer === null) {
            beginInterval();
        } else {
            stopInterval();
        }
    })
});